package com.cn.utils;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;

public class PacketSecret {
    private OperationMode mode;//要使用的加密运行模式
    private SecretKey secretKey;//DES使用的密钥
    private Cipher enCipher, deCipher;//加密/解密算法,这里选用DES
    private static final byte[] IV = {1, 2, 3, 4, 5, 6, 7, 8};
    private static byte count;//记录填充数

    public PacketSecret() {
        this(new ECB());
    }

    public PacketSecret(OperationMode mode) {
        this.mode = mode;
        initDES();
    }

    public void setMode(OperationMode mode) {
        this.mode = mode;
    }

    public OperationMode getMode() {
        return mode;
    }

    //初始化密钥和加密算法
    private void initDES() {
        try {
            //原始密钥，至少8个
            byte[] rawKey = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
            DESKeySpec deskeySpec = new DESKeySpec(rawKey);
            secretKey = SecretKeyFactory.getInstance("DES").generateSecret(deskeySpec);
            enCipher = Cipher.getInstance("DES/ECB/NoPadding");//这里只使用普通的DES模式
            deCipher = Cipher.getInstance("DES/ECB/NoPadding");
            enCipher.init(Cipher.ENCRYPT_MODE, secretKey);
            deCipher.init(Cipher.DECRYPT_MODE, secretKey);
        } catch (Exception e) {
            System.out.println("DES算法初始化失败");
            e.printStackTrace();
        }
    }

    //对分组数据预处理，以输入加密函数
    private byte[] initPacket(byte[] data) {
        if (data.length % 8 == 0) return data;
        //如果刚好64一组则直接进行加密，否则先填充
        byte[] newData = new byte[((data.length >> 3) + 1) * 8];
        System.arraycopy(data, 0, newData, 0, data.length);//默认填充0
        count = (byte) (8 - data.length % 8);//记录填充了多少数据
        return newData;
    }

    public byte[] packetEncrypt(byte[] data) {
        byte[] packets = initPacket(data);
        try {
            byte[] encryptData = mode.packetEncrypt(packets, enCipher);
            return encryptData;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public byte[] packetDecrypt(byte[] packets) {
        if (packets.length % 8 != 0) {
            System.out.println("解密数据长度错误");
            return null;
        }

        try {
            byte[] rawData = mode.packetDecrypt(packets, deCipher, enCipher);
            if (count == 0) {
                return rawData;
            } else {
                return Arrays.copyOf(rawData, rawData.length - count);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void arraysXOR(byte[] src, byte[] des) {
        if (src.length != des.length) {
            System.out.println("数组长度不一致无法异或");
            return;
        }
        for (int i = 0; i < src.length; i++) {
            src[i] ^= des[i];
        }
    }

    static class ECB implements OperationMode {
        @Override
        public byte[] packetEncrypt(byte[] data, Cipher enCipher) throws Exception {
            byte[] encryptData = new byte[data.length];
            byte[] encryptBlock, block;
            //循环加密，每8字节一组
            for (int i = 0; i < data.length; i += 8) {
                block = Arrays.copyOfRange(data, i, i + 8);//获取分组
                encryptBlock = enCipher.doFinal(block);//进行加密
                //将数据拷贝到输出数组
                System.arraycopy(encryptBlock, 0, encryptData, i, encryptBlock.length);
            }
            return encryptData;
        }

        @Override
        public byte[] packetDecrypt(byte[] data, Cipher deCipher, Cipher enCipher) throws Exception {
            byte[] rawData = new byte[data.length];
            byte[] decryptBlock, block;
            //循环加密，每8字节一组
            for (int i = 0; i < data.length; i += 8) {
                block = Arrays.copyOfRange(data, i, i + 8);//获取分组
                decryptBlock = deCipher.doFinal(block);//进行解密
                //将数据拷贝到输出数组
                System.arraycopy(decryptBlock, 0, rawData, i, decryptBlock.length);
            }
            return rawData;
        }
    }

    static class CBC implements OperationMode {

        @Override
        public byte[] packetEncrypt(byte[] data, Cipher enCipher) throws Exception {
            byte[] encryptData = new byte[data.length];
            byte[] encryptBlock, block;
            byte[] iv = Arrays.copyOf(IV, IV.length);
            //循环加密，每8字节一组
            for (int i = 0; i < data.length; i += 8) {
                block = Arrays.copyOfRange(data, i, i + 8);//获取分组
                arraysXOR(block, iv);//保存异或结果到block
                encryptBlock = enCipher.doFinal(block);//对结果进行加密
                iv = encryptBlock;//保存结果加密到iv
                //将数据拷贝到输出数组
                System.arraycopy(encryptBlock, 0, encryptData, i, encryptBlock.length);
            }
            return encryptData;
        }


        @Override
        public byte[] packetDecrypt(byte[] data, Cipher deCipher, Cipher enCipher) throws Exception {
            byte[] rawData = new byte[data.length];
            byte[] decryptBlock, block;
            byte[] iv = Arrays.copyOf(IV, IV.length);
            //循环加密，每8字节一组
            for (int i = 0; i < data.length; i += 8) {
                block = Arrays.copyOfRange(data, i, i + 8);//获取分组
                decryptBlock = deCipher.doFinal(block);//先进行解密
                arraysXOR(decryptBlock, iv);//将异或结果保存到decryptBlock
                iv = block;
                //将数据拷贝到输出数组
                System.arraycopy(decryptBlock, 0, rawData, i, decryptBlock.length);
            }
            return rawData;
        }
    }

    public static class CFB implements OperationMode {
        @Override
        public byte[] packetEncrypt(byte[] data, Cipher enCipher) throws Exception {
            byte[] encryptData = new byte[data.length];
            byte[] encryptBlock, block;
            byte[] iv = Arrays.copyOf(IV, IV.length);
            //循环加密，每8字节一组
            for (int i = 0; i < data.length; i += 8) {
                block = Arrays.copyOfRange(data, i, i + 8);//获取分组
                encryptBlock = enCipher.doFinal(iv);//对IV进行加密
                arraysXOR(encryptBlock, block);//保存异或结果到encryptBlock
                iv = encryptBlock;//保存密文结果到iv
                //将数据拷贝到输出数组
                System.arraycopy(encryptBlock, 0, encryptData, i, encryptBlock.length);
            }
            return encryptData;
        }

        @Override
        public byte[] packetDecrypt(byte[] data, Cipher deCipher, Cipher enCipher) throws Exception {
            byte[] rawData = new byte[data.length];
            byte[] decryptBlock, block;
            byte[] iv = Arrays.copyOf(IV, IV.length);
            //循环加密，每8字节一组
            for (int i = 0; i < data.length; i += 8) {
                block = Arrays.copyOfRange(data, i, i + 8);//获取分组
                iv = enCipher.doFinal(iv);//获取到加密后的IV
                arraysXOR(block, iv);//先将异或结果保存到Block
                decryptBlock = block;
                iv = Arrays.copyOfRange(data, i, i + 8);
                //将数据拷贝到输出数组
                System.arraycopy(decryptBlock, 0, rawData, i, decryptBlock.length);
            }
            return rawData;
        }
    }

    static class OFB implements OperationMode {
        @Override
        public byte[] packetEncrypt(byte[] data, Cipher enCipher) throws Exception {
            byte[] encryptData = new byte[data.length];
            byte[] encryptBlock, block;
            byte[] iv = Arrays.copyOf(IV, IV.length);
            //循环加密，每8字节一组
            for (int i = 0; i < data.length; i += 8) {
                block = Arrays.copyOfRange(data, i, i + 8);//获取分组
                iv = enCipher.doFinal(iv);//对IV进行加密
                encryptBlock = Arrays.copyOf(iv, iv.length);
                arraysXOR(encryptBlock, block);//保存异或结果到encryptBlock
                //将数据拷贝到输出数组
                System.arraycopy(encryptBlock, 0, encryptData, i, encryptBlock.length);
            }
            return encryptData;
        }

        @Override
        public byte[] packetDecrypt(byte[] data, Cipher deCipher, Cipher enCipher) throws Exception {
            //OFB模式下加解密过程是一致的
            return packetEncrypt(data, enCipher);
        }
    }

    public static void printArrays(byte[] data) {
        for (int i = 0; i < data.length; i++) {
            System.out.print(String.format("0x%02x,", data[i]));
        }
        System.out.println();
    }
    public static void printText(byte[] data) {
        for (int i = 0; i < data.length; i++) {
            System.out.print(data[i]);
        }
        System.out.println();
    }

    public static void imageTest(File infile, File outFile, Mode mode) {
        PacketSecret secret = new PacketSecret();
        secret.setMode(new ECB());

        try (//自动关闭流
             FileInputStream fis = new FileInputStream(infile);
             FileOutputStream fos = new FileOutputStream(outFile);
        ) {

            FileChannel infileChannel = fis.getChannel();
            FileChannel outfileChannel = fos.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 5);//读入5k到缓冲区
            byte[] secretText;
            while (infileChannel.read(byteBuffer) != -1) {//读取到缓冲区
                byte[] data = byteBuffer.array();

                if (mode == Mode.Encrypt) {
                    secretText = secret.packetEncrypt(data);
                } else {
                    secretText = secret.packetDecrypt(data);
                }
                byteBuffer.rewind();//将指针指向头部
                byteBuffer.put(secretText);
                byteBuffer.flip();//设置limit指针到position，准备读出
                outfileChannel.write(byteBuffer);
                byteBuffer.clear();
            }

            infileChannel.close();
            outfileChannel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void simpleTest() {
        byte[] text = "zhangsan&20Aa".getBytes();//明文内容
        PacketSecret secret = new PacketSecret();
        secret.setMode(new ECB());
        test(secret, text);
        secret.setMode(new CBC());
        test(secret, text);
        secret.setMode(new CFB());
        test(secret, text);
        secret.setMode(new OFB());
        test(secret, text);
    }

    public static void test(PacketSecret secret, byte[] text) {
        System.out.println("-------当前运行模式:" + secret.getMode().getClass().getSimpleName() + "-------");
        byte[] pe = secret.packetEncrypt(text);
        System.out.println("----密文如下:----");
        String encodeToString = Base64.getEncoder().encodeToString(pe);
        System.out.println(encodeToString);
        System.out.println("----解密后如下:----");
        byte[] decode = Base64.getDecoder().decode(encodeToString);
        System.out.println(new String(secret.packetDecrypt(decode), StandardCharsets.UTF_8));
    }


    public static void main(String[] args) {
      simpleTest();//字节数组测试

      // //在桌面放一张名为test.jpg的图片
      // System.out.println("在桌面放一张名为test.jpg的图片");
      // File file = new File(FileSystemView.getFileSystemView().getHomeDirectory(), "test.jpg");
      // //输出到桌面
      // File EncryptFile = new File(FileSystemView.getFileSystemView().getHomeDirectory(), "test-Encrypt.jpg");
      // File DecryptFile = new File(FileSystemView.getFileSystemView().getHomeDirectory(), "test-Decrypt.jpg");

      // System.out.println("加密图片文件路径:" + EncryptFile.getPath());
      // imageTest(file, EncryptFile, Mode.Encrypt);//输出加密图片
      // imageTest(EncryptFile, DecryptFile, Mode.Decrypt);//输出解密图片
    }

    enum Mode {Decrypt, Encrypt}

    //运作模式通用接口，采用"命令设计模式"
    public interface OperationMode {
        byte[] packetEncrypt(byte[] data, Cipher cipher) throws Exception;//加密分组
        byte[] packetDecrypt(byte[] data,Cipher deCipher,Cipher enCipher) throws Exception;//解密分组
    }
}

